from copy import deepcopy
from datetime import datetime

from django.utils import tree
from django.core.exceptions import FieldError
from django.db import connection
from django.db.models.fields import Field, FieldDoesNotExist
from django.db.models.query_utils import QueryWrapper

class Expression(object):
    """
    Base class for all sql expressions, expected by QuerySet.update.
    """
    # Arithmetic connection types
    ADD = '+'
    SUB = '-'
    MUL = '*'
    DIV = '/'
    MOD = '%%'

    # Bitwise connection types
    AND = '&'
    OR = '|'

    def _combine(self, other, conn, reversed, node=None):
        if reversed:
            obj = ExpressionNode([Literal(other)], conn)
            obj.add(node or self, conn)
        else:
            obj = node or ExpressionNode([self], conn)
            if isinstance(other, Expression):
                obj.add(other, conn)
            else:
                obj.add(Literal(other), conn)
        return obj

    def __add__(self, other):
        return self._combine(other, self.ADD, False)

    def __sub__(self, other):
        return self._combine(other, self.SUB, False)

    def __mul__(self, other):
        return self._combine(other, self.MUL, False)

    def __div__(self, other):
        return self._combine(other, self.DIV, False)

    def __mod__(self, other):
        return self._combine(other, self.MOD, False)

    def __and__(self, other):
        return self._combine(other, self.AND, False)

    def __or__(self, other):
        return self._combine(other, self.OR, False)

    def __radd__(self, other):
        return self._combine(other, self.ADD, True)

    def __rsub__(self, other):
        return self._combine(other, self.SUB, True)

    def __rmul__(self, other):
        return self._combine(other, self.MUL, True)

    def __rdiv__(self, other):
        return self._combine(other, self.DIV, True)

    def __rmod__(self, other):
        return self._combine(other, self.MOD, True)

    def __rand__(self, other):
        return self._combine(other, self.AND, True)

    def __ror__(self, other):
        return self._combine(other, self.OR, True)

    def as_sql(self, opts, field, prep_func=None, qn=None):
        raise NotImplementedError

class ExpressionNode(Expression, tree.Node):
    default = None

    def __init__(self, children=None, connector=None, negated=False):
        if children is not None and len(children) > 1 and connector is None:
            raise TypeError('You have to specify a connector.')
        super(ExpressionNode, self).__init__(children, connector, negated)

    def _combine(self, *args, **kwargs):
        return super(ExpressionNode, self)._combine(node=deepcopy(self), *args, **kwargs)

    def as_sql(self, opts, field, prep_func=None, qn=None, node=None):
        if not qn:
            qn = connection.ops.quote_name
        if node is None:
            node = self

        result = []
        result_params = []
        for child in node.children:
            if hasattr(child, 'as_sql'):
                sql, params = child.as_sql(opts, field, prep_func, qn)
                format = '%s'
            else:
                sql, params = self.as_sql(opts, field, prep_func, qn, child)
                if len(child.children) > 1:
                    format = '(%s)'
                else:
                    format = '%s'
            if sql:
                result.append(format % sql)
                result_params.extend(params)
        conn = ' %s ' % node.connector
        return conn.join(result), result_params

class Literal(Expression):
    """
    An expression representing the given value.
    """
    def __init__(self, value):
        self.value = value

    def as_sql(self, opts, field, prep_func=None, qn=None):
        if self.value is None:
            return 'NULL', ()

        if isinstance(self.value, datetime):
            sql = connection.ops.datetime_cast_sql()
        else:
            sql = '%s'
        params = prep_func and prep_func(self.value) or (self.value,)

        if isinstance(params, QueryWrapper):
            return params.data
        return sql, params

class F(Expression):
    """
    An expression representing the value of the given field.
    """
    def __init__(self, name):
        self.name = name

    def as_sql(self, opts, field, prep_func=None, qn=None):
        if not qn:
            qn = connection.ops.quote_name

        try:
            src_field = opts.get_field(self.name)
        except FieldDoesNotExist:
            names = opts.get_all_field_names()
            raise FieldError('Cannot resolve keyword %r into field. '
                    'Choices are: %s' % (self.name, ', '.join(names)))

        field_sql = connection.ops.field_cast_sql(src_field.db_type())
        lhs = '%s.%s' % (qn(opts.db_table), qn(src_field.column))
        return field_sql % lhs, ()
